Автоматическая генерация кода
=============================

Начиная с версии 1.1.2, в состав Yii входит веб-инструмент для генерации кода,
называемый *Gii*. Он заменяет существовавший до этого консольный генератор
`yiic shell`. В данном разделе описано, как использовать Gii и как расширить его
для ускорения разработки.

Использование Gii
-----------------

Gii является модулем и должен быть использован в составе существующего приложения Yii.
Для использования Gii необходимо отредактировать файл конфигурации приложения
следующим образом:

~~~
[php]
return array(
	…
	'modules'=>array(
		'gii'=>array(
			'class'=>'system.gii.GiiModule',
			'password'=>'задайте свой пароль',
			// 'ipFilters'=>array(…список IP…),
			// 'newFileMode'=>0666,
			// 'newDirMode'=>0777,
		),
	),
);
~~~

Выше мы объявили модуль с именем `gii` и классом [GiiModule]. Также мы задали
пароль, который будет использоваться для доступа к Gii.

По умолчанию, в целях безопасности, Gii доступен только для localhost.
Если необходимо дать доступ к нему с других компьютеров, нужно задать
свойство [GiiModule::ipFilters] как показано в коде выше.

Так как Gii будет генерировать и сохранять новые файлы с кодом в существующее
приложение, необходимо убедиться в том, что процесс веб-сервера имеет на это права.
Показанные выше свойства [GiiModule::newFileMode] и [GiiModule::newDirMode]
содержат права, с которыми будут создаваться файлы и директории.

> Note|Примечание: Gii является инструментом разработчика. Поэтому он должен быть
> установлен исключительно на компьютере или сервере разработчика. Так как
> он может генерировать новые скрипты PHP, необходимо уделить особое внимание
> безопасности (пароль, IP фильтры).

Теперь можно запустить Gii по URL `http://hostname/path/to/index.php?r=gii`, где
`http://hostname/path/to/index.php` — URL вашего приложения.

Если существующее приложение использует формат URL `path`
(см. [красивые адреса URL](/doc/guide/topics.url)), мы можем запустить Gii по URL
`http://hostname/path/to/index.php/gii`. Может понадобиться добавить следующие правила
URL перед уже существующими:

~~~
[php]
'components'=>array(
	…
	'urlManager'=>array(
		'urlFormat'=>'path',
		'rules'=>array(
			'gii'=>'gii',
			'gii/<controller:\w+>'=>'gii/<controller>',
			'gii/<controller:\w+>/<action:\w+>'=>'gii/<controller>/<action>',
			…существующие правила…
		),
	),
)
~~~

В составе Gii есть готовый набор генераторов кода. Каждый генератор отвечает за
свой тип кода. К примеру, генератор контроллера создаёт класс контроллера
вместе с несколькими шаблонами отображения; генератор модели создаёт класс ActiveRecord
для определённой таблицы БД.

Последовательность работы с генератором следующая:

1. Зайти на страницу генератора;
2. Заполнить поля, которые задают параметры генерируемого кода. К примеру,
для генерации модуля необходимо указать его ID;
3. Нажать кнопку `Preview` для предварительной оценки генерируемого кода.
Вы увидите таблицу файлов, которые будут сгенерированы и сможете просмотреть их
код;
4. Нажать кнопку `Generate` для создания файлов;
5. Просмотреть журнал генерации кода.

> Note|Примечание: после генерации модели стоит проверить и скорректировать метод `rules` так как структура базы данных
часто не содержит достаточно данных о требованиях валидации.

Расширение Gii
--------------

Несмотря на то, что включённые в состав Gii генераторы создают достаточно
функциональный код, часто требуется его немного изменить или создать новый
генератор по своему вкусу и потребностям. К примеру, нам может понадобиться
изменить стиль генерируемого кода или добавить поддержку нескольких языков.
Всё это может быть легко реализовано через Gii.

Gii можно расширять двумя способами: изменяя существующие шаблоны кодогенераторов
и создавая свои генераторы.

###Структура кодогенератора

Генератор кода размещается в директории, чьё имя является именем генератора.
Директория обычно содержит:

~~~
model/                       корневая директория генератора модели
   ModelCode.php             модель, используемая для генерации кода
   ModelGenerator.php        контроллер кодогенератора
   views/                    отображения генератора
      index.php              шаблон по умолчанию
   templates/                шаблоны кода
      default/               набор шаблонов 'default'
         model.php           шаблон для генерации класса модели
~~~

###Путь поиска генераторов

Gii ищет генераторы в списке директорий, указанных в свойстве
[GiiModule::generatorPaths]. В том случае, если необходимо добавить
свои генераторы, следует настроить приложение следующим образом:

~~~
[php]
return array(
	'modules'=>array(
		'gii'=>array(
			'class'=>'system.gii.GiiModule',
			'generatorPaths'=>array(
				'common.gii',   // псевдоним пути
			),
		),
	),
);
~~~

Приведённые выше настройки заставляют Gii искать генераторы в директории
с псевдонимом `common.gii` в дополнение к стандартным `system.gii.generators` и `application.gii`.

Возможно иметь несколько одноимённых генераторов, если у них разные пути
поиска. В этом случае будет использоваться генератор, путь поиска которого
указан выше в [GiiModule::generatorPaths].


###Изменение шаблонов кода

Изменение шаблонов кода — самый простой и самый распространённый путь расширения
Gii. Мы будем использовать примеры для того, чтобы описать, как изменить шаблоны
кода. Допустим, нам необходимо изменить код, создаваемый генератором модели.

Сначала мы создаём директорию `protected/gii/model/templates/compact`. Здесь `model`
означает, что мы собираемся *перекрыть* генератор модели по умолчанию.
А `templates/compact` — что мы добавляем новый набор шаблонов кода `compact`.

После этого мы добавляем в настройки приложения в свойство [GiiModule::generatorPaths]
значение `application.gii`, как показано в предыдущем подразделе.

Теперь открываем страницу генератора модели. Щёлкаем на поле `Code Template`.
Вы должны увидеть выпадающий список, содержащий нашу только что созданную директорию
шаблонов `compact`. Тем не менее, если мы выберем этот шаблон, будет выведена
ошибка. Происходит это потому, что в наборе `compact` ещё нет самих шаблонов кода.

Скопируем файл `framework/gii/generators/model/templates/default/model.php` в
`protected/gii/model/templates/compact`. Если попробовать сгенерировать код
с набором `compact` ещё раз, генерация должна пройти успешно. Тем не менее,
генерируемый код ничем не отличается от кода, получаемого из набора `default`.

Время сделать некоторые изменения.
Откроем файл `protected/gii/model/templates/compact/model.php`. Данный файл будет
использован как шаблон отображения, что означает, что он может содержать
выражения и код PHP. Изменим шаблон таким образом, что метод `attributeLabels()`
генерируемого кода будет использовать `Yii::t()` для перевода заголовков полей:

~~~
[php]
public function attributeLabels()
{
	return array(
<?php foreach($labels as $name=>$label): ?>
			<?php echo "'$name' => Yii::t('application', '$label'),\n"; ?>
<?php endforeach; ?>
	);
}
~~~

В каждом шаблоне кода у нас есть доступ к некоторым предопределённым переменным,
таким как, например, `$labels`. Эти переменные задаются соответствующим генератором
кода. Разные генераторы могут предоставлять шаблонам различные наборы переменных.
Стоит внимательно изучить описание шаблонов кода по умолчанию.


###Создание новых генераторов

В этом подразделе мы покажем, как реализовать новый генератор, который сможет
создавать новые классы виджетов.

Сначала создадим директорию `protected/gii/widget`. В ней создадим следующие файлы:

* `WidgetGenerator.php`: содержит класс контроллера `WidgetGenerator`, который
является входной точкой генератора виджетов.
* `WidgetCode.php`: содержит класс модели `WidgetCode`, который отвечает за
логику генерации кода.
* `views/index.php`: отображение, содержащее форму ввода генератора.
* `templates/default/widget.php`: шаблон кода по умолчанию для генерации класса
виджета.


#### Реализация `WidgetGenerator.php`

Файл `WidgetGenerator.php` предельно простой. Он содержит лишь следующий код:

~~~
[php]
class WidgetGenerator extends CCodeGenerator
{
	public $codeModel='application.gii.widget.WidgetCode';
}
~~~

Здесь мы описываем, что генератор будет использовать класс модели, чей псевдоним
пути `application.gii.widget.WidgetCode`. Класс `WidgetGenerator` наследуется
от [CCodeGenerator], реализующего большое количество функций, включая
действия контроллера, необходимые для координации процесса генерации кода.

#### Реализация `WidgetCode.php`

Файл `WidgetCode.php` содержит класс модели `WidgetCode`, в котором реализована
логика генерации класса виджета на основе полученных от пользователя параметров.
В данном примере будем считать, что единственное, что вводит пользователь —
имя класса виджета. `WidgetCode` выглядит следующим образом:

~~~
[php]
class WidgetCode extends CCodeModel
{
	public $className;

	public function rules()
	{
		return array_merge(parent::rules(), array(
			array('className', 'required'),
			array('className', 'match', 'pattern'=>'/^\w+$/'),
		));
	}

	public function attributeLabels()
	{
		return array_merge(parent::attributeLabels(), array(
			'className'=>'Widget Class Name',
		));
	}

	public function prepare()
	{
		$path=Yii::getPathOfAlias('application.components.' . $this->className) . '.php';
		$code=$this->render($this->templatepath.'/widget.php');

		$this->files[]=new CCodeFile($path, $code);
	}
}
~~~

Класс `WidgetCode` наследуется от [CCodeModel]. Как и в обычном классе модели, в
данном классе мы реализуем методы `rules()` и `attributeLabels()` для валидации
ввода и генерации подписей полей соответственно. Стоит отметить, что так как
базовый класс [CCodeModel] уже описывает некоторое количество правил валидации
и названий подписей, то мы должны объединить их с нашими правилами и подписями.

Метод `prepare()` подготавливает код к генерации. Главная задача метода — подготовить
список объектов [CCodeFile], каждый из которых представляет будущий файл с кодом.
В нашем примере необходимо создать всего один объект [CCodeFile], представляющий
класс виджета, который будет сгенерирован в директории `protected/components`.
Для непосредственной генерации кода используется метод [CCodeFile::render].
Данный метод содержит PHP-шаблон кода и возвращает сгенерированный код.


#### Реализация `views/index.php`

После реализации контроллера (`WidgetGenerator`) и модели (`WidgetCode`)
самое время заняться отображением `views/index.php`:

~~~
[php]
<h1>Генератор виджета</h1>

<?php $form=$this->beginWidget('CCodeForm', array('model'=>$model)); ?>

	<div class="row">
		<?php echo $form->labelEx($model,'className'); ?>
		<?php echo $form->textField($model,'className',array('size'=>65)); ?>
		<div class="tooltip">
			Класс виджета должен содержать только буквы.
		</div>
		<?php echo $form->error($model,'className'); ?>
	</div>

<?php $this->endWidget(); ?>
~~~

В данном коде мы отображаем форму, используя виджет [CCodeForm]. В этой форме мы
показываем поле для ввода атрибута `className` модели `WidgetCode`.

При создании формы мы можем использовать две замечательные возможности [CCodeForm].
Одна — подсказки для полей. Вторая — запоминание введённых значений.

Если вы использовали один из стандартных генераторов кода, вы могли заметить красивые
всплывающие подсказки, появляющиеся рядом с полем при получении им фокуса. Использовать
данную возможность очень легко: достаточно после поля вставить `div` с CSS классом `tooltip`.

Для некоторых полей полезно запомнить последнее верное значение и тем самым позволив
пользователю не вводить значения повторно каждый раз, когда он использует генератор.
Примером может служить поле ввода базового класса контроллера в стандартном генераторе.
Такие поля изначально отображаются как подсвеченный статичный текст. При щелчке
они превращаются в поля ввода.

Для того, чтобы сделать поле запоминаемым, необходимо сделать две вещи.

Во-первых, нужно описать правило валидации `sticky` для соответствующего атрибута
модели. К примеру, для стандартного генератора контроллера используется приведённое
ниже правило для запоминания атрибутов `baseClass` и `actions`:

~~~
[php]
public function rules()
{
	return array_merge(parent::rules(), array(
		…
		array('baseClass, actions', 'sticky'),
	));
}
~~~

Во-вторых, в отображении необходимо добавить CSS класс `sticky` контейнеру `div`
поля ввода:

~~~
[php]
<div class="row sticky">
	…поле ввода…
</div>
~~~

#### Реализация `templates/default/widget.php`

Наконец, мы создаём шаблон кода `templates/default/widget.php`. Как было описано
ранее, он используется как PHP-шаблон отображения. В шаблоне кода мы всегда
можем обратиться к переменной `$this`, которая содержит экземпляр модели кода.
В нашем примере `$this` содержит объект `WidgetModel`. Таким образом, мы можем
получить введённый пользователем класс виджета через `$this->className`.

~~~
[php]
<?php echo '<?php'; ?>

class <?php echo $this->className; ?> extends CWidget
{
	public function run()
	{

	}
}
~~~

На этом реализация генератора кода завершена. Обратиться к нему можно по
URL `http://hostname/path/to/index.php?r=gii/widget`.